package com.atguigu.gmall.order.service.impl;


import com.atguigu.gmall.common.constant.RedisConst;
import com.atguigu.gmall.feign.cart.CartFeignClient;
import com.atguigu.gmall.feign.product.SkuInfoFeignClient;
import com.atguigu.gmall.feign.ware.WareFeignClient;
import com.atguigu.gmall.model.enums.OrderStatus;
import com.atguigu.gmall.model.enums.ProcessStatus;
import com.atguigu.gmall.model.order.OrderDetail;
import com.atguigu.gmall.model.order.OrderInfo;
import com.atguigu.gmall.mq.RabbitConst;
import com.atguigu.gmall.order.config.AppOrderConfig;
import com.atguigu.gmall.order.config.OrderProperties;
import com.atguigu.gmall.order.mapper.OrderDetailMapper;
import com.atguigu.gmall.order.mapper.OrderInfoMapper;
import com.atguigu.gmall.order.service.OrderService;
import com.atguigu.gmall.order.to.OrderSpiltDetailTO;
import com.atguigu.gmall.order.to.OrderSplitTo;
import com.atguigu.gmall.order.to.WareSkuTo;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectReader;
import com.sun.org.apache.bcel.internal.generic.NEW;
import io.swagger.models.auth.In;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import javax.xml.crypto.Data;
import javax.xml.soap.Detail;
import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.*;

@Component
public class OrderServiceImpl extends ServiceImpl<OrderInfoMapper,OrderInfo> implements OrderService {

    //新的线程池
    ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(10);

    @Autowired
    StringRedisTemplate redisTemplate;

    @Autowired
    SkuInfoFeignClient skuInfoFeignClient;

    @Autowired
    WareFeignClient wareFeignClient;

    @Autowired
    CartFeignClient cartFeignClient;

    @Autowired
    ThreadPoolExecutor executor;

    @Autowired
    OrderProperties orderProperties;

    @Autowired
    OrderInfoMapper orderInfoMapper;

    @Autowired
    OrderDetailMapper orderDetailMapper;

    @Autowired
    RabbitTemplate rabbitTemplate;

    @Override
    public boolean checkToken(String tradeNo, String userId) {
        ValueOperations<String, String> operations = redisTemplate.opsForValue();
        //原子验令牌
        //前端带请求进来，拿着令牌就删除
        String script = "if redis.call('get', KEYS[1]) == ARGV[1] " +
                "then return redis.call('del', KEYS[1]) else return 0 end";
        /*String token = operations.get(RedisConst.USER_UNREPEAT_TOKEN + userId);
        //令牌用完就删，防止前端恶意重试
        redisTemplate.delete(RedisConst.USER_UNREPEAT_TOKEN + userId);*/
        Long execute = redisTemplate.execute(
                new DefaultRedisScript<Long>(script, Long.class),
                Arrays.asList(RedisConst.USER_UNREPEAT_TOKEN + userId), tradeNo);

        if (!StringUtils.isEmpty(tradeNo) && execute.intValue() != 0){
            //验令牌成功
            return true;
        }

        return false;
    }

    @Override
    public ArrayList<String> checkSkuPriceAndStock(List<OrderDetail> list) {
        //集合保存之前，我们给线程池提交的所有异步任务
        List<CompletableFuture<Void>> futures = new ArrayList<>();
        List<String> errMsg = new ArrayList<>();

        if (!CollectionUtils.isEmpty(list)){
            //遍历每一个需要购买的商品
            for (OrderDetail orderDetail : list) {
                Long skuId = orderDetail.getSkuId();
                String skuName = orderDetail.getSkuName();
                BigDecimal orderPrice = orderDetail.getOrderPrice();
                //验库存
                Integer skuNum = orderDetail.getSkuNum();

                //【用户体验】 验证所有，即使失败，继续往下校验
                //获取sku的最新价格
                CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
                    //不用老请求就可以
                    BigDecimal skuPrice = skuInfoFeignClient.getSkuPrice(skuId);
                    if (!orderPrice.equals(skuPrice)) {
                        errMsg.add(skuName + "价格发生变化,请刷线确认");
                    }
                }, executor);


                //【用户体验】验库存 远程系统支持连验证+锁库存一起做？
                //分布式锁+数据库事物，除了减库存，还要生成各种其它信息
                CompletableFuture<Void> future1 = CompletableFuture.runAsync(() -> {
                    String stock = wareFeignClient.hasStock(skuId, skuNum);
                    if (stock.equals("0")) {
                        errMsg.add(skuName + "库存不足");
                    }
                }, executor);
                futures.add(future);
                futures.add(future1);

            }
        }
        //以前收集到的所有异步任务全部结束，才能算结束
        //CompletableFuture[] objects = (CompletableFuture[]) futures.toArray();
        CompletableFuture[] futureArray = new CompletableFuture[futures.size()];
        for (int i = 0; i < futures.size(); i++) {
            futureArray[i] = futures.get(i);
        }
        CompletableFuture.allOf(futureArray).join();
        return (ArrayList<String>) errMsg;
    }

    /**
     * 创建订单 order_detail order_info
     * @param orderInfo
     * @return
     */
    @Override
    public OrderInfo createOrder(OrderInfo orderInfo) {
        //1、保存订单信息 order_info
        //设置订单总金额
        orderInfo.sumTotalAmount();
        //订单状态 添加name不会因为版本而改变
        orderInfo.setOrderStatus(OrderStatus.UNPAID.name());
        //out_trade_no 和支付宝对接的唯一流水号
        //给支付宝一个你订单的唯一标识（对外流水号） 问支付宝某个单是否支付成功
        //订单id
        orderInfo.setOutTradeNo("ATGUIGU"+System.currentTimeMillis()+ new Random().nextInt(10000));
        //订单创建时间 create_time
        Date date = new Date();
        orderInfo.setCreateTime(date);

        //订单过期时间  expire_time
        Calendar instance = Calendar.getInstance();
        //当前时间+30min
        instance.add(Calendar.MINUTE,orderProperties.getTtl());
        Date time = instance.getTime();
        orderInfo.setExpireTime(time);

        //设置订单状态 process_status
        orderInfo.setProcessStatus(ProcessStatus.UNPAID.name());
        List<OrderDetail> detailList = orderInfo.getOrderDetailList();
        //get(0) 表示商品第一个图片
        orderInfo.setImgUrl(detailList.get(0).getImgUrl());

        int insert = orderInfoMapper.insert(orderInfo);

        //2、保存订单详情: 保存订单中的所有商品信息
        for (OrderDetail orderDetail : detailList) {
            //商品id
            Long id = orderDetail.getId();
            //订单id里面放商品id
            orderDetail.setOrderId(id);
            orderDetail.setCreateTime(new Date());
        }
        //批量保存订单详情的方法，只和数据库交互一次
        orderDetailMapper.saveBatch(detailList);

        afterOrderCreate(orderInfo);
        return orderInfo;
    }

    @Override
    public void closeOrder(Long id) {
        //去数据库修改订单状态
//        if ()  //判断如果当前订单是UPAID
        //扫描所有未关闭的订单
        OrderInfo orderInfo = orderInfoMapper.selectById(id);
        if(orderInfo.getOrderStatus().equals(OrderStatus.UNPAID.name())){
            String name = OrderStatus.CLOSED.name();
            orderInfoMapper.updateOrderStatus(id,name);
        }
    }

    @Override
    public void changeOrderStatusPayed(String out_trade_no, String name) {
        orderInfoMapper.updateOrderStatusPayed(out_trade_no,name);
    }

    @Override
    public OrderInfo getOrderInfoWithDetailsByOutTradeNo(String outTradeNo) {
        //查出订单信息
        QueryWrapper<OrderInfo> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("out_trade_no", outTradeNo);
        OrderInfo orderInfo = orderInfoMapper.selectOne(queryWrapper);

        //查出订单详情
        QueryWrapper<OrderDetail> wrapper = new QueryWrapper<>();
        wrapper.eq("order_id",orderInfo.getId());
        List<OrderDetail> detailList = orderDetailMapper.selectList(wrapper);
        orderInfo.setOrderDetailList(detailList);
        return orderInfo;
    }

    @Override
    public List<OrderSplitTo> splitOrder(Long orderId, String wareSkuMap) throws JsonProcessingException {
        //1、原始订单信息
        OrderInfo orderInfo = getOrderInfoWithDetailsById(orderId);

        //2、订单中商品的库存分布   转换成复杂类型
        List<WareSkuTo> wareSkuTos = new ObjectMapper().readValue(wareSkuMap, new TypeReference<List<WareSkuTo>>() {
        });

        //3、拆单  支付成功后，库存系统扣库存时就会进行拆单
        //所有拆后的单

        ArrayList<OrderSplitTo> splitTos = new ArrayList<>();
        for (WareSkuTo wareSkuTo : wareSkuTos) {
            OrderSplitTo splitTo = new OrderSplitTo();
            //仓库id
            String wareId = wareSkuTo.getWareId();
            //上方仓库的所有商品id
            List<String> skuIds = wareSkuTo.getSkuIds();
            //TODO 把原始订单拆分成多个继续保存数据库
            OrderInfo info = new OrderInfo();
            //因为收货人等信息是一样的，所以需要复制信息  orderInfo复制到info
            BeanUtils.copyProperties(orderInfo,info);
            //新订单id自增
            info.setId(null);
            //原始订单是父订单
            info.setParentOrderId(orderInfo.getId());
            //1、算总价的前提是知道商品信息
            QueryWrapper<OrderDetail> queryWrapper = new QueryWrapper<>();
            queryWrapper.in("sku_id",skuIds);
            queryWrapper.eq("order_id",orderInfo.getId());
            List<OrderDetail> detailList = orderDetailMapper.selectList(queryWrapper);
            info.setOrderDetailList(detailList);

            //2、重新计算拆分的订单的价格,就必须保证setOrderDetailList有东西
            info.sumTotalAmount();

            //记录拆分了的这个单是为了哪个仓库拆分的

            //拆分的单放进集合
            //3、保存拆分后的单
            orderInfoMapper.insert(info);

            //4、封装库存服务的信息
            //
            BeanUtils.copyProperties(info,splitTo);
            splitTo.setPaymentWay("2");
            splitTo.setWareId(wareId);

            //把刚才插入的子订单id获取到
            splitTo.setOrderId(info.getId().toString());
            //拆完订单以后，每个订单中包含哪些商品
            List<OrderSpiltDetailTO> detail = new ArrayList<>();
            for (OrderDetail orderDetail : detailList) {
                detail.add(new OrderSpiltDetailTO(orderDetail.getSkuId().toString(),orderDetail.getSkuNum(),orderDetail.getSkuName()));
            }
            splitTo.setDetails(detail);

            //收集所有拆了的单
            splitTos.add(splitTo);
        }
        //改变大单状态,改为已拆分
        orderInfoMapper.updateOrderStatus(orderInfo.getId(), OrderStatus.SPLIT.name());

        return splitTos;
    }

    @Override
    public void changeOrderStatus(long parseLong, OrderStatus waitingDelever) {
        orderInfoMapper.updateOrderStatus(parseLong,waitingDelever.name());
    }

    @Override
    public OrderInfo getOrderInfoWithDetailsById(Long orderId) {
        //用订单id查到订单信息
        OrderInfo orderInfo = orderInfoMapper.selectById(orderId);


        QueryWrapper<OrderDetail> queryWrapper = new QueryWrapper<>();
        //查询订单详情条件 OrderDetail
        queryWrapper.eq("order_id",orderId);

        //查询订单详情
        List<OrderDetail> detailList = orderDetailMapper.selectList(queryWrapper);
        orderInfo.setOrderDetailList(detailList);
        return orderInfo;
    }

    private void afterOrderCreate(OrderInfo orderInfo) {


        //1、把购物车中相关的商品删除即可
        List<Long> skuIds = new ArrayList<>();
        List<OrderDetail> detailList = orderInfo.getOrderDetailList();

        for (OrderDetail orderDetail : detailList) {
            skuIds.add(orderDetail.getSkuId());
        }
        //远程带哦用之前，feign的拦截器自己会把老请求放过去
        //从购物车中删除相关商品
        cartFeignClient.deleteSkuFromCart(skuIds);

        //2、发送一个消息
        rabbitTemplate.convertAndSend(RabbitConst.ORDER_EXCHANGE,RabbitConst.CREATE_ORDER_RK,orderInfo.getId());






    }
}
